Installation
按照官网教程安装,可全局和单次,这里是用全局安装
terminal 输入 laravel 查看命令
laravel new projectName可以用server 软件,如MAPP 启动,也可以用自带的
php artisan serve启动启动时,默认的目录是 project/public 文件夹
Basic Knowledge
Route & View
Route
Route 在
app -> Http -> Request -> route.php下,具体形式如Route::get('/', function () { return view('welcome'); });- 收到 “/” 请求,执行回调函数
- 返回一个叫做 “welcome” 的view
- 也可以直接返回html, 如
return "<h1>Hello World</h1>"
View
View 在
resources -> views下,具体形式为welcome.blade.php- 如果view的位置在更深的目录,如
resources -> views -> pages -> about.blade.php,那么在router里可以写成:return view('pages.welcome')
使用了blade 模版引擎的一个view,在route 中可以用全称,也可以省略后缀
//TODO view 可以理解成一个页面 还是 一个组件呢?
- 如果view的位置在更深的目录,如
Data & Blade
Data
data可以在定义router的时候被赋给不同的view, 如下
Route::get('/datablade', function(){ $people = ['Tom', "Ethan", "Eddy", "Cory"]; return view('page.datablade', compact('people')); });这样,在datablade 这个view里面,我们就可以使用people里的数据了
导入数据的方式还有很多种, 如
['people'=>$people],->with('people'->$people)等等Blade
Blade 是laravel 所使用的模版引擎,可以在blade.php 中写html 和 php,php需要 key wrod 和 closure key work 包裹起来使用,如
<div class="container"> @foreach($people as $person) <li>{{ $person }}</li> @endforeach </div>
所有@keywords 必须有 @endkeywords 来结束,中间可以把变量包裹在 {{}} 符号里
常用的keyword 如 if, for, foreach, unless 等等
Route & Controller
Controller 是将所有router 内函数定义信息分割出来的东西,看例子比较清晰
Laravel 会自动将服务器请求的数据转换为JSON格式,特别适合做API
首先,在 router.php 中,把原先的router 改成如下:
// Route::get('/datablade', function(){ // $people = ['Tom', "Ethan", "Eddy", "Cory"]; // return view('page.datablade', compact('people')); // }); Router::get('/', 'PagesController@datablade');其中,‘PagesController’ 是一个Controller,@datablade 是指其中对应该页面的方法
创建Controller
controller 的路径:
app -> Http -> Controller -> Controller.php自定义的话可以用 php artisan 来创建一个 controller 类,如
$php artisan make:controller PagesController定义PagesController
class PagesController extends Controller { public function datablade(){ $people = ['This', "Is", "Using", "Controller"]; return view('pages.datablade', compact('people')); } }这里datablade 里的内容其实和原先router 里function内容一样,单独拎出来,叫做*“定义信息分割出来”*
这样做的意义在于,如果要做的事情很多,比如处理request请求,就不适合放在router里面
Url 指向不同内容时
很多时候我们需要
http://example.com/card/1这样的URL,这时在定义router 时我们可以如下获取参数Router::get('card/{cardId}', 'CardsController@show');这样card 后面的内容就会以 cardId 的名字存下来,可以在controller 里面进行操作,如下:
public function show($id){ $card = Card::find($id); return view(cards.show, compact('card')); }或者,有一种更好的方法,利用Eloquent 建的model 来做:
public function show(Card $cardId){ return view(cards.show, compact('cardId')); }这种方法直接找到 cardId 所对应的object,命名为$cardId,但是需注意,这里的命名需要和touter 里面的命名一致才可以,还需要有前面建好的Card 类
View
Layout Files (Master Page)
炒鸡实用的工具,想象每个页面都有一堆<mate>, <link>, <script>, 然后当我们需要修改其中任何一个,我们就必须修改每一个页面,非常麻烦,Layout file 很好的解决了这个痛点
在view 中新建一个
layout.blade.php,并放入head 文件以及任何每个页面共有的内容,如下<!DOCTYPE html> <html> <head> <mate view="portview" width="width-width" initial-scale=1 user-scalable=0> <title>Laravel</title> <link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css"> <link href="aa/bb/cc/abc.css" rel="stylesheet"> <script type="text/javascript" src="...../..../..../.js"></script> @yield('headerSection') </head> <body> @yield('bodySection') @yield('footerSection') <script type="text/javascript" src="...../..../..../.js"></script> <script type="text/javascript" src="...../..../..../.js"></script> <script type="text/javascript" src="...../..../..../.js"></script> </body> </html>这个页面包含了所有页面公有的一些信息,把每个页面不同的body 留出来,叫做 “bodySection” (可自定义)
在view 的每个页面中,修改成如下信息
@extends('layout') @section('bodySection') <div class="container"> <div class="content"> <div class="title">Laravel 5</div> </div> </div> @stop其中,
@extends('layout')会将layout file 里面包含的头文件等信息全部放进来,而我们的主要内容就放在@section('bodySection')和@stop之间,这样就可以专注于内容任何关于头文件的修改,只要在layout file 里面改一次即可全面应用
headerSection 以及 footerScetion 可以用于特定页面需要加载特定内容的情况,与body用法一样
Database
database config coule be found at config -> database.php
1. 选择要使用的数据库
'default' => env('DB_CONNECTION', 'mysql'),
在这里选择默认的数据库类型,具体类型可参见下面connection里的内容,如sqllite,pgsql 等等
'connections' => [
'mysql' => [
'driver' => 'mysql',
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
'strict' => false,
'engine' => null,
],
...
2. config 数据库
上面的代码里,注意env() 方法是读取到 /.env 文件中的配置
env('DB_HOST', 'localhost'), 的意思是在.env 配置文件中找不到DB_HOST项的话,默认使用第二个参数
.env 文件如下所示
...
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravelTest
DB_USERNAME=root
DB_PASSWORD=********
...
基于这个机制,我们可以在推送代码到git 或者给别人时,直接ingore 这个.env 文件,只有在部署服务器的时候新建一个.env 文件,最大程度的保障了数据的安全
3. 使用Migration
Migration是一个数据库的版本管理工具,你可以rollback,reset等,它给予一种代码实现和命令行结合的方式来管理你的数据库。

Migration 的位置在 database -> Magration ,默认有user 和 password,可以删掉重新建
建立magration
$php artisan make:migration create_Cards_table --create='cards'可以在新建的magration 中添加字段
Laravel 约定,数据表用首字母小写复数,model用首字母大写单数
更新migrate
如果在已经建好表的情况下想要更新表的结构,需要新建一个update migrate
$php artisan make:migration update_Cards_table在里面定义 更新
up和 rollbackdownfunction 去实现更新操作,如/** * Run the migrations. * * @return void */ public function up() { Schema::table('blogs', function(Blueprint $table) { $table->string('createDate')->nullable()->change(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('blogs', function(Blueprint $table) { $table->string('createDate')->nullable(false)->change(); }); }然后执行
$php artisan migrate操作
建表:
$php artisan migrate回滚:
$php artisan migrate:rollbackTinker
artisan 的一种命令行工具,通过
$php artisan tinker打开可以执行运算、php命令等等,下面的query-builder 就可以在tinker中先测试和修改
可以使用
Control + C退出tinker
4. Query Builder
insert:
DB::table('Card')->insert(['title' => 'new card', 'created_at' => new DateTime, 'Updated_at' => new DateTime]);
select:
DB::table("Card_Table")->get()
DB::table("Card_Table")->where("title", "new card")->first()
delete:
DB::table("Card_Table")->where("title", "new card")->delete()
5. Fetch Data
Query Builder 可以被写在 Controller 里面,如
class CardsController extends Controller
{
public function index(){
$cards = DB::table('Card_Table')->get();
return view('cards.index', compact('cards'));
}
}
如果报错找不到DB,则使用 use DB 在命名空间下面
6. Eloquent
Eloquent 是laravel 系统的ORM (Object Relation Mapping 关系映射),是laravel 最强大的地方
可以通过Eloquent 建 model的方式快速读取数据库,我们的 Model 类将继承自 Eloquent 提供的 Model 类,预置了很多异常强大的函数,从此想干啥事儿都是一行代码就搞定。
为某一张表新建model类
$php artisan make:model Card这里 Card 是model 名字,Laravel 约定俗成数据表的名字小写复数,model的名字大写单数,这里对应的是表Cards
新建好的model 在
app -> Card.php如果数据表和model 的名字不符合规定,则需要在model 里面加上
protected $table = 'tableName';来手动指定表格,建议每个model 都加上,保证准确性还可以通过
php artisan make:model name -m的方式快速同时生成model 和 migration在对应controller 里配置Eloquent 并访问数据
use App\Card; ... public function index(){ // 原先是: $cards = DB::table('cards')->get(); $cards = Card::all(); return view('cards.index', compact('cards')); }一些简单操作的例子
$card = Card::find(2); //找到id 为2 的card $card = Card::where('title', '标题')->first(); //找到title 为‘标题’ 的card $cards = Card::all(); //返回全部card (此处是一个对象集合,可以加上->toArray() 转换为数组) $cards = Card::where('id', '>', 10)->where('id', '<', 20)->orderBy('updated_at', 'desc')->get(); //找到所有 id 大于10,小于20 的card,并且按照update_at 倒叙排序除了all 和 find方法外,其它所有的方法如where,orderBy 结尾的内容都要加上get() 或者 find() 来触发数据库查找的动作
7. Eloquent Relationship
在数据库操作中,把表格用foreign-key 连接在一起是很常见的操作
这里我们有一个 一对多 对应关系做例子:
card: id, ….
note: id, …, card_id
在Card 的model 里,可以写一个关系对应函数:
class Card extends Model
{
public function notes() //必须叫做notes 指向改表 不能修改
{
return $this->hasMany(Note::Class);
//hasOne/Many(Note, forigen_key, local_key)
}
}
其中,Note::Class 还可以写成等价的 'App\Note'
这样,在Tinker里就可以测试 App\Card::find(3) -> notes; 会得到一个对应notes 的collection
这里有一个小tricks,就是notes 是否需要带括号
$card->notes会返回一个collection,是一个sql语句查询的结果$card->notes()会返回一个方法:Illuminate\Database\Eloquent\Relations\HasMany$card->notes->first()会返回第一个note,但是是在查询得到所有notes后把第一个取出来$card->notes()->first()会返回第一个note,但是是在SQL加入了条件,只从数据库里取到了一个数据两个方法效率截然不同,用的场合也不一样
反之,Note Model 也需要一个方法去联系到相应的 Card
class Note extends Model
{
public function card() //cards 指向改表 不能修改
{
return $this->belongsTo(Card::class);
}
}
所有Eloquent 的返回结果均为collection object,有超级多的方法,比数据库好!
1 to 1
case: Phone table has a column called ‘user_id’, refers to the id column in User table
class User extends Model {
public function phone() {
return $this->hasOne('App\Phone');
}
}
$phone = User::find(1)->phone;
如果 “user_id” 叫做 “other_id”,则使用 return $this->hasOne('App\Phone', 'other_id')
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
反之,我们还以通过phone 找到相应的user,叫做belongTo
class Phone extends Model {
public function user() {
return $this->belongTo('App\User')
}
}
1 to many
一个user,可能对应多个phone
return $this->hasMany('App\Phone');
这个时候,所有user_id 为 1 的Phone 记录都会被返回
$phones = App\User::find(1)->phones;
foreach ($phones as $phone) {...}
反过来多对一,也是belongsTo 方法
Many to many
Case: User has many role_id, and specific role can refers to many people, so there’re 3 Tables:Users, Roles and user_role, 其中user 有id,roles 也有id,user-role 表把他们联系在一起
class User extends Model {
public function roles() {
return $this->belongsToMany('App\Role');
}
}
$user = App\User::find(1);
$roles = $user->roles->orderBy('name')->get(); //or
foreach ($user->roles as $role){...}
Eloquent 会自动找 user_role table (named in alphabetical order),但是也可以自己定义表名和字段名
return $this->belongsToMany('App\Role', 'role_user_table', 'user_id', 'role_id');
其中,那个中间连接表,可以被一个pivot (轴承) 方法获取,如
$user = App\User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
} // 每一个实例都有一个pivot 方法指向相应连接表的记录
中间表也可以有自己的model,but it need inherit “Pivot” instead of “Model”
使用时也要using model rather than the table
return $this->belongsToMany('App\User')->using('App\UserRole');
A to B to C
case: C table has a b_id column, B tabel has a a-id column, then we can use hasManyThrough to get C from A
class A extends Model {
public function C() {
return $this->hasManyThrough('App\C', 'App\B');
// App\C is the final data we want
// App\B is the intermedia table we use
}
}
Polymorphic 1 to Many
Case: comments table can be used to both posts and videos
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
class Comment extends Model
{
// Get all of the owning commentable models.
public function commentable()
{
return $this->morphTo();
}
}
<------------------------------------>
class Post extends Model
{
// Get all of the post's comments.
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
<------------------------------------>
class Video extends Model
{
// Get all of the video's comments.
public function comments()
{
return $this->morphMany('App\Comment', 'commentable');
}
}
$post = App\Post::find(1);
foreach ($post->comments as $comment) {...}
Polymorphic many to many
case: posts and videos can have many tags, we use the “taggables” table conect them
posts
id - integer
name - string
videos
id - integer
name - string
tags
id - integer
name - string
taggables
tag_id - integer
taggable_id - integer
taggable_type - string
class Post extends Model
{
// Get all of the tags for the post.
public function tags()
{
return $this->morphToMany('App\Tag', 'taggable');
}
}
Eager Load
在有很多很多联系的时候,我们可以用eager load 来快速获取相关的信息
如下例子,Card 对应很多 Note, 每一个Note 对应一个 Author
$card = APP\Card->find(1);
//我们可以通过如下方式取得 $card 的对应的 users
return $card->notes[0]->user;
//也可以使用eager load 取得同样的效果
return $card->load('notes.user');
//Eager Load, join 两张表获取所有的信息
$card->with('note')->get();
8. Eloquent Create Records
在上面例子里,我们可能需要创建新的note,这时我们需要在note model 里面允许编辑,如下
class Note extends Model
{
protected $fillable = ['body'];
public function card(){
return $this->belongsTo(Card::class);
}
}
$fillable 使得我们有权限编辑 ’body‘ 字段
这样我们在tinker 里可以使用$card->notes()->create(['body'=>'yet another note about this card']); 来为card添加note,也就可以使用在页面逻辑中
9. Test Data
laravel 允许我们通过factories 工具构建大量测试数据
在生成model 后,可以在database->factories->ModelFactory.php 中构建想要的数据
之后可以在make seed 或者tanker 中生成数据,例如在tinker中使用:
factory('App\Task', 20)->make()就可以生成20条tasksfactory('App\Task', 20)->create()就可以生成20条tasks 并存入数据库
Route
这里需要总结一下Route了,总结一下一个请求的过程中数据的走向
这样的一个地址,http://learnlaravel:1234/cards/13 laravel 会自动寻找
CardModel如果有, 那么
- Router:
Route::get('cards/{card}', 'CardsController@show'); - Controller:
public function store(Request $request, Card $card){..}
- Router:
在上面这个过程里,浏览器发起请求,
{card}被赋值为 13;controller 去Card Model 里面找
数据库cards 表里,id 为1的那个数据,提取出来赋给 $cardcontroller 拿到 $card 后,想返回到一个view 里:
return view('card.whatever', compact('card'));$card 就被传递给 whatever 这个view里面了。
这个时候,在这个view 里面,我们就可以直接使用
{{$card->XXX}} 去调用数据了
Form
在view 里添加 form 表单
show.blade.php:
<form method="POST" action="/cards/{{ $cardId->id }}/notes"> <div class="form-group"> <textarea name="body" class="form-control"></textarea> {{--for CSRF Protection--}} <input type="hidden" name="_token" value="{{ csrf_token() }}"> </div> <div class="form-group"> <button type="submit" class="btn btn-danger" >Add Note</button> </div> </form>这样的例子会post 一个请求到 localhost/cards/2/notes
post 的内容是一个JSON: {body: textarea content, _token: ……}
所以需要一个对应的路由去接管 “ localhost/cards/2/notes ” 这个地址
Route::post('cards/{cardId}/notes', 'NotesController@store');因为是post请求,所以前缀要有post,对应到一个新的controller 下面的store 方法
(如果是新建controller,用
$php artisan make:controller NotesController)新建store方法:
use App\Card; use App\Note; ... public function store(Request $request, Card $cardId){ //another way is " return /Request::all(); " //return $request->all(); //Approach 1: //$note = new Note; //$note->body = $request->body; //$cardId -> notes() -> save($note); //Approach 2: //$cardId -> notes() -> save( // new Note(['body' => $request->body]) //); //Approach 3: //$note = new Note(['body' => $request->body]); //Approach 4: //$cardId -> notes() -> create({ // 'body' => $request->body; //}) //Approach 5: $cardId -> notes() -> create($request->all()); return back(); //or return redirect()->to('url'); }有多种方法可以取到request内容,或者直接添加到card 中
需要 $cardId 信息需要先添加 use App\Card
最后一种方法需要注意,一般的php 不能这样操作,因为会加入其他信息,如_token。
但是laravel 允许这样做因为我们在之前限定了
$fillable = ['body']所以只有body会被提交
Validate the Form
在获得用户id 的时候,建议不要直接在Html 里写
<input type="hidden" name="user_id" value="xxx">, 因为用户可以在提交表单的时候通过修改html 表单去伪装登陆。这种情况下,我们可以在保存note 的方法里写入user 信息,如下
NotesController:
public function store(Request $request, Card $cardId){ $note = new Note($request->all()); $note->user_id = Auth::user_id; $cardId -> notes() -> save($note); return back(); }这样用户就无法自己修改登录用户信息了
在store note 的时候,有时需要保证一些规则,如某些值不为空等等
可以查看validate 文档,有很多功能,这里简单的写个例子
NotesController -> store
$this->validate($request, [ 'body' => 'required|min:10' // check "Laravel Available Validation Rules" ]);这样body 输入的内容不合法的时候不会保存,会返回原来页面,可以通过全局变量 $error 来查看错误信息
也可以在Html 上显示错误信息,如下例
@if(count($errors)) <div class="alert alert-warning" role="alert"> @foreach($errors->all() as $error) <p>{{ $error }}</p> @endforeach </div> @endif
Core Concept
IoC (Inversion of Control)
用来降低代码间的耦合度
理解:
类A 要依赖BCDEF,所以一般来说,需要在实例化 A 的时候同时实例化BCDEF

但是控制反转究竟反转了什么呢?依赖对象的获得被反转了
In step1,A 获取了 BCDEF,A 可以随意创建或者使用BCDEF,主动权在A,但是高度耦合
但是通过控制反转,我们通过第三方,在外部实例化好BCDEF,注入到A 的五个私有对象中,降低耦合

此时A 不再有主动权(因为它不控制BCDEF),控制权在第三方手里,他在A 需要的时候实例化一个B 实例给A,所以称作控制反转
超人的例子
很累的超人
class Superman { protected $power; public function __construct() { $this->power = array( new Fight(9, 100), new Force(45), new Shot(99, 50, 2) ); } }一般的超人,很厉害但是很累,一旦修改了power 的参数,例如现在
fight(speed, holdtime)变成了flight(strength, speed, holdtime),这样我们不仅要修改flight 类,同时要修改超人类用个工厂模式吧
class SuperModuleFactory { public function makeModule($moduleName, $options) { switch ($moduleName) { case 'Fight': return new Fight($options[0], $options[1]); case 'Force': return new Force($options[0]); case 'Shot': return new Shot($options[0], $options[1], $options[2]); } } } class Superman { protected $power; public function __construct() { // 初始化工厂 $factory = new SuperModuleFactory; // 通过工厂提供的方法制造需要的模块 $this->power = array( $factory->makeModule('Force', [45]), $factory->makeModule('Shot', [99, 50, 2]) ); } }好像也没有太大区别,只是不用写很多new 关键字,其实再换一种写法就清楚了
新写法 你会懂
class Superman { protected $power; public function __construct(array $modules) { // 初始化工厂 $factory = new SuperModuleFactory; // 通过工厂提供的方法制造需要的模块 foreach ($modules as $moduleName => $moduleOptions) { $this->power[] = $factory->makeModule($moduleName, $moduleOptions); } } } // 创建超人 $superman = new Superman([ 'Fight' => [9, 100], 'Shot' => [99, 50, 2] ]);这样就好很多了。我们不用改写超人类了,只需要在建造超人的时候选择power 就好,很简单
Dependcy Injection
控制反转是一种思想,而依赖注入是实现它的一种途径。
Laravel 通过接口实现依赖注入
超能力接口
interface SuperModuleInterface { /** * 超能力激活方法 * * 任何一个超能力都得有该方法,并拥有一个参数 *@param array $target 针对目标,可以是一个或多个,自己或他人 */ public function activate(array $target); } class XPower implements SuperModuleInterface { public function activate(array $target){...} } class UltraBomb implements SuperModuleInterface { public function activate(array $target){...} }所有的超能力都必须走这个借口,这样保证了超能力的格式一致性,不管你是什么超能力,都可以被超人接受。
超人对接接口
class Superman { protected $module; public function __construct(SuperModuleInterface $module) { $this->module = $module } } // 超能力模组 $superModule = new XPower; // 初始化一个超人,并注入一个超能力模组依赖 $superMan = new Superman($superModule); // or $superManNo2 = new Superman(UltraBomb $ultraBomb)现在超人只接受一个SuperModuleInterface 对象作为参数,其他类型的参数都会报错。
至此依赖注入结束,我们从超人类自己手动定义依赖,到自己选择想要的依赖进行注入,彻底解耦。
IoC Container
上述的生产方法还是需要 实例化 一个超能力对象,再注入到SuperMan 里,手动行还是比较多,不够自动。
是时候来一个更高级的工厂模式 - IoC Container (laravel 中叫做 service container)
// 一个最简陋的IoC 模型
class Container {
protected $binds; // array,绑定的类
protected $instances; // array,类的实例化集合
/**
* 向 Container 添加/绑定一种对象的生产方式
*
* @abstract 一个字符串(或接口), 当你需要 make 这个类的对象的时候, 传入这个字符串(或者接口)
* 这样make 就知道制造什么样的对象了
* @concrete 一般为一个 Closure 或者 一个单例对象, 用于说明制造这个对象的方式
**/
public function bind ($abstract, $concrete) {
if ($concrete instanceof Closure) { // 闭包,即检查 $concrete 的值是不是一个匿名函数
$this->binds[$abstract] = $concrete; // 若是闭包,添加到binds 集合中
} else {
$this->instances[$abstract] = $concrete; // 若是实例,说明是实例对象,添加到实例集合中
}
}
/**
* 通过上述的绑定,生产相应的对象
*
* @abstract 与上述相同,字符串或接口表示这个类的名称
* @parameters array 在生产这个类时需要的参数,默认为空
**/
public function make ($abstract, $parameters = []) {
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
array_unshift($parameters, $this);
return call_user_func_array($this->binds[$abstract], $parameters)
}
}
上述就是一个最简单的容器,有bind 和make 方法,使用如下
$container = new Container; // 创建一个上面的container
// 绑定 superman,bind 后面接一个闭包,返回 SuperMan 加 make
$container->bind('superman', function ($container, $moduleName)) {
return new SuperMan($container->make($modoleName));
}
// 绑定技能,bind 后面接实例,往superman 里添加能力
$container->bind('xpower', function ($container) {
return new XPower;
});
$container->bind('ultrabomb', function($container) {
return new UltraBomb;
});
// ****************** 华丽丽的分割线 **********************
// 开始启动生产
$superman_1 = $container->make('superman', 'xpower');
$superman_2 = $container->make('superman', 'ultrabomb');
$superman_3 = $container->make('superman', 'xpower');
// ...随意添加
这个时候Superman 和 技能彻底解耦,没有相互依赖的关系,只需要通过注册、绑定的方法向容器里添加一段可以被执行的回调,作为生产一个类实例的脚本,就可以通过make 方法去实时调用。
至此,Laravel 核心IoC Container 基本建立,其他的功能模块,如Route, Eloquent ORM, Request and Response 等都是与核心无关的模块,这些类从注册到实例化,都是容器负责。
Service Provider
Laravel 的核心是上面说的IoC Container,但是它封装了注册的过程,用一个简单的 ServiceProvider 去完成绑定注册的过程。
每一个Module 都有一个对应的XXXServiceProvider 继承ServiceProvider,用于注册和绑定。 ServiceProvider 包括连个部分,Register 注册 和 Boot 初始化
- Register 负责向容器注册脚本
- Boot 会在所有Module 注册之后调用,所以在这里可以调用其他的Module
有了相应的XXX ServiceProvider 之后,我们在 Config\app 中的 Provider 对象里面注册每一个module 的 ServiceProvider, 如 AuthServiceProvider, RouteServiceProvider 甚至 BentoboxServiceProvider 等,真正把service 注册到container中。
Others
Authentication
一般在项目最初的时候就应该进行,不建议在项目一半的时候进行
$php artisan make:auth- config database, and migrate by default
Session
session flash: 只在访问特定url时出现一次,刷新就没有了,可用于用户刚注册完成的界面
Route::get('begin', function(){ Session::flash('status', 'hello'); return redirect('/home'); }); // 在对应的home view 里面,应该包含如下: @if(Session::has('status')) <h3>{{ Session::get('status') }}</h3> @endif
MiddleWare
middleware 可以用来控制访问的一个中间连接件
举例来说,用户登录可以看到自己的信息,但是没有登录的用户,如果访问了查看信息的url,需要一层中间件来把他隔离出来。Laravel 自带的Auth 模块很好的利用middleware 解决了这个问题
app -> http -> middleware -> authenticate.php:
class Authenticate
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
}
return redirect()->guest('login');
}
return $next($request);
}
}
解析:
1. 如果用户发起一个请求,判断是不是`guest()`
2. 如果是,判断是不是要请求信息或数据
- 如果是,返回401 Unauthorized
- 如果不是,返回到login 页面要求登录
3. 如果不是,说明是已经登录的用户,使用$next 进行下一步操作
如何使用?
就上面例子来说,我们可以在 app -> http -> Kernel.php 里面看到如下
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
...
];
上面的Authenticate 中间件被赋了一个名字 ‘auth’
方法一:
这样,可以在router.php 里写如下:
Route::get('dashboard', 'HomeController@index') -> middleware('auth');这样在访问dashboard 的时候,会先访问middleware auth,检查是不是登录,再进行下一步
方法二 (推荐):
这样,可以在需要的controller里,如HomeController.php 开头写下:class HomeController extends Controller { /** * Create a new controller instance. * * @return void */public function __construct() { $this->middleware('auth'); }… }
这样HomeController 下面所有的方法在被调用时都会先走中间件auth 当然还可以用类似 `$this->middleware('auth',['except' => ['index', 'xxx']])` 来排除某些方法 或者用 `$this->middleware('auth', ['only'=>['index', 'xxx'])` 来指定使用某些方法
自定义一个middleware
$php artisan make:middleware MustBeAdminapp -> http -> middleware -> mustbeadmin.php
public function handle($request, Closure $next) { $user = $request -> user(); if($user && $user->name == 'Ethan'){ return $next($request); } abort(404, "no way"); }
1. app -> http -> kernel.php
```php
protected $routeMiddleware = [
...
'admin' => \App\Http\Middleware\MustBeAdmin::class,
}
certain controller:
public function __construct() { $this->middleware('admin'); }
放在global HTTP middleware 会自动套用在所有route 上
依赖注入 (Dependency Injection)
当使用的类拥有非常多的依赖类时,调用(new) 这个类就会需要把它依赖的所有类new 一遍,如下
class UserRegis() {
protected $mailer
public function __construct(Mailer $mailer){
$this->mailer = $mailer;
}
}
//in other places
$userregis = new UserRegis(new Mailer(new Foo(new Bar)))
这样在依赖很多的时候就很复杂
所有Laravel 利用依赖注入的方式,简化了这个流程,在app service provider 里面绑定依赖即可,如
App::bind('UserRegis', function(){
return new UserRegis(new Mailer(new Foo(new Bar)))
})
//in other place
$userregis = app('UserRegis')
//or
public function test(UserRegis $userregis){
$userregis....
}
bind 表示所有实例各自独立,也可以用singleton方法,表示所有new的结果都是同一个实例
其实,如果都是实体类,可以省略绑定的步骤,直接public function test(UserRegis $userregis){} 也能找到这个类,但是如果使用的是抽象类或者接口,那么就一定需要去绑定一次。
Interface
接口(interface) 就像是RPG游戏中的装备系统,比如说“防具”,“武器”,“魔法”,就是所谓的接口,你可以装备 各种对应的道具,比如“橡木盾”,“屠龙刀”,“亡灵法杖”,这些就是实现(implementation)。接口的作用就是解耦,简单理解就是“装备”,实现就是具体装备的实体类,只要是按照接口换装,英雄不会出现无法使用武器的情况。
Laravel 里,首先要定义一个Interface,里面有需要实现的方法,如
namespace App\Api;
interface DiorApiInterface {
public function test();
public function isMember($openId, $mobile);
public function getMemberProfile($memberId);
public function processGiftReservation($params);
public function getActiveRedemptionGiftList($giftId);
}
然后要有至少一个实现类去实现这个接口,接口里的每一个类都需要实现,如
class Case1 implements DiorApiInterface{
public function test(){
return "this is DiorApi Class";
}
......
}
再然后,要在app service provider 里面绑定接口
public function boot() {
$this->app->bind('App\Api\DiorApiInterface', function(){
if(Env::isDebug()){
return new Case1();
}
return new Case2();
});
}
自动化工具 Gulp in Laravel
不能相信 laravel 居然连gulp 也集成进来了 ORZ,而且主要功能封装的很简单
安装
使用 npm install 去安装 package.json 里依赖的包 ORZORZORZ
在gulpfile.js 里,设置需要的自动化内容,如
var elixir = require('laravel-elixir'); elixir(function(mix) { mix.sass('app.scss'); });exlixir 是 laravel 集成的一个工具,简化了自动化操作
上述自动化设置了将
resources -> asset -> app.scss自动编译的功能只需要在laravel 项目文件夹路径内,使用 “gulp” 命令,就会自动执行
- 使用 gulp —production 命令,还会自动加上压缩功能
- 使用 gulp watch 命令,就会自动watch… 不能更方便了…
还集成了很多其它功能,参见 https://laravel.com/docs/5.2/elixir
artisan 工具
Artisan 是 Laravel 内建的命令行工具,它提供了一些有用的命令协助您开发
在laravel 目录下使用 php artisan list 查看命令列表
可以创建server,
创建controller php artisan make:controller等
可以用 php artisan help make:controller 来查看命令的使用方法
Maintain mode
$php artisan down会使网站进入维护界面,期间可以修改数据库等操作$php artisan up网站上线
Helper 自定义工具集
有时候需要很多自定义的工具函数需要随时调用,laravel 提供了一个方法可以全局调用这写方法
在APP 下新建一个PHP文档,如helper.php,并写入自己的工具函数
在composer.json 里, 找到autoload,写入:
"autoload": { ..., "files":[ "app/helper.php" ] },$composer dump-autoload
完成
Static HTML Cache
将静态页面定期每小时生成缓存,利用 .htaccess 重定向功能使静态页面的访问直接走缓存,加快存取速度。
How To
配置middleware,定期生成缓存
php artisan make:middleware CacheStaticHtmlc新建middleware查找缓存路径,如果没有找到(该时段第一次访问),则删除所有已过期缓存,新建HTML并缓存
public function handle(Request $request, Closure $next) { if ($request->method() == 'GET') { $response = $next($request); $day = str_pad(date('d'), 2, '0', STR_PAD_LEFT); $hour = str_pad(date('H'), 2, '0', STR_PAD_LEFT); $url = preg_replace('/https?:\/\//', '', $request->url()); $cacheDirectory = public_path() . '/cache/html/'; $directory = $cacheDirectory . $day . '/' . $hour . '/' . $url; //if no such file, delete all expired cache, then create a new cache if (!File::isDirectory($directory)) { if (File::isDirectory($cacheDirectory)) File::deleteDirectory($cacheDirectory); File::makeDirectory($directory, 0777, true); File::put($directory . '/index.html', $response->getContent()); } } return $next($request); }在kernel.php 中注册middleware
'cacheStatic' => [ CacheStaticHtml::class, ],
配置 .htaccess 文件
如果改地址有缓存文件存在,则不经过路由,直接读取缓存HTML
RewriteEngine On # Rewrite to html cache if it exists and the request is off a static page # (no url query params, no session cookies and only get requests) # A new cache will be created every hour RewriteCond %{REQUEST_METHOD} GET RewriteCond %{DOCUMENT_ROOT}/cache/html/%{TIME_DAY}/%{TIME_HOUR}/%{HTTP_HOST}/$1/index.html -f RewriteRule ^(.*)$ cache/html/%{TIME_DAY}/%{TIME_HOUR}/%{HTTP_HOST}/$1/index.html [L]将需要缓存的路由放在该middleware下
Route::group(['middleware' => ['cacheStatic']], function () { // 专属资讯 Route::get('/mc/home', 'DiorMemberHomeController@index'); // 专属资讯 -> 专柜活动 Route::get('/mc/activity', 'DiorMemberHomeController@activity'); // 专属资讯 -> 会员优享活动 Route::get('/mc/advantage', 'DiorMemberHomeController@advantage'); // 专属资讯 -> 当季热品 Route::get('/mc/products', 'DiorMemberHomeController@products'); // 专属资讯 -> 当季热品 -> 热品详情 Route::get('/mc/products/{id}', 'DiorMemberHomeController@productsDetail'); });
Notice
Nginx 服务器可以使用 该网址 自动生成htaccess
在更新静态页面时,记得及时删除cache 文件夹重新生成新缓存。